<template>
  <view class="flex" :class="[props.direction == 'vertical' ? 'flex-row' : 'flex-col', _disabled ? 'opacity-6' : '']">
    <view
      class="relative flex flex-col"
      :class="[props.direction == 'horizontal' ? 'flex-col-center-start' : 'flex-row-center-center']"
      :style="[{ width: _sliderBarCssWidth + 'rpx', height: _sliderBarCssHeight + 'rpx' }]"
    >
      <tm-sheet
        no-level
        :round="10"
        :color="props.bgColor"
        :height="props.direction == 'horizontal' ? props.height : _sliderBarCssHeight"
        :width="props.direction == 'horizontal' ? _sliderBarCssWidth : props.height"
        :margin="[0, 0]"
        :padding="[0, 0]"
      ></tm-sheet>
      <slider-bar
        :follow-theme="props.followTheme"
        :class="[props.direction == 'horizontal' ? 'flex-col-center-start' : 'flex-row-center-center']"
        :direction="props.direction"
        :color="props.color"
        :size="props.height"
        :x="_barLet"
        :width="_barWidth"
      ></slider-bar>
      <slider-mask
        v-if="props.showLabel"
        :follow-theme="props.followTheme"
        :color="props.color"
        :size="props.buttonSize"
        :step="props.step"
        :min="props.min"
        :max="props.max"
        :width="_sliderBarCssWidth"
        :height="_sliderBarCssHeight"
        :direction="props.direction"
      ></slider-mask>
      <slider-button
        ref="btn0"
        :max-left="buttonStaticsMaxLeft"
        :follow-theme="props.followTheme"
        :direction="props.direction"
        :color="props.color"
        :x="btnPos[0].x"
        :size="props.buttonSize"
        @movestart="butnMoveStart($event, 0)"
        @moveing="butnMove($event, 0)"
        @moveend="butnMoveEnd($event, 0)"
      ></slider-button>
      <slider-button
        v-if="isDablue"
        ref="btn1"
        :max-left="buttonStaticsMaxLeft"
        :follow-theme="props.followTheme"
        :direction="props.direction"
        :color="props.color"
        :x="btnPos[1].x"
        :size="props.buttonSize"
        @movestart="butnMoveStart($event, 1)"
        @moveing="butnMove($event, 1)"
        @moveend="butnMoveEnd($event, 1)"
      ></slider-button>
    </view>
    <view v-if="props.showLabel" :class="[props.direction == 'vertical' ? 'flex-col' : 'flex-row']">
      <slider-label
        :size="props.buttonSize"
        :step="props.step"
        :min="props.min"
        :max="props.max"
        :width="_sliderBarCssWidth"
        :height="_sliderBarCssHeight"
        :direction="props.direction"
      ></slider-label>
    </view>
    <view
      v-if="showDetail"
      class="flex absolute"
      :class="[props.direction == 'horizontal' ? 'flex-col flex-col-start-center' : ' flex-row ']"
      :style="[
        props.direction == 'horizontal'
          ? { width: props.width + props.buttonSize + 'rpx' }
          : { height: props.width + props.buttonSize + 'rpx' },
      ]"
    >
      <view
        :class="[isNvue ? 'fixed' : 'absolute ', props.direction == 'horizontal' ? (isNvue ? 't-0' : 'b-0') : 't-0 ']"
        class="mb-0 flex flex-col flex-col-bottom-center"
        :style="[
          !isNvue
            ? props.direction == 'horizontal'
              ? {
                  transform: `translateX(${btnPos[BtnIndex].x}px)`,
                  left: -(100 - props.buttonSize + 24) / 2 + 'rpx',
                }
              : {
                  transform: `translateY(${btnPos[BtnIndex].x}px)`,
                  top: '-70rpx',
                  left: -(100 - props.buttonSize + 24) / 2 + 'rpx',
                }
            : { left: `${nvueDetailPos.left}px`, top: `${nvueDetailPos.bottom}px` },
        ]"
      >
        <tm-sheet
          :follow-dark="false"
          :dark="false"
          _class="flex-center"
          color="grey-darken-5"
          :border="2"
          :margin="[0, 0]"
          :padding="[10, 6]"
          :width="100"
          :round="3"
        >
          <tm-text :label="_value"></tm-text>
        </tm-sheet>
        <tm-icon
          :follow-dark="false"
          :dark="false"
          color="grey-darken-5"
          _class="t--10"
          :font-size="32"
          name="tmicon-sort-down-nogap-copy"
        ></tm-icon>
      </view>
    </view>
  </view>
</template>
<script lang="ts" setup>
/**
 * 滑块
 * description 注意，如果想开启单滑块（默认），default-value=0单个值即可，如果想双滑块，值等于数组比如:=[0,5]
 */
import { computed, ref, Ref, toRaw, getCurrentInstance, watchEffect, watch, inject, PropType } from 'vue'
import { inputPushItem, rulesItem } from './../tm-form-item/interface'
import sliderBar from './slider-bar.vue'
import sliderButton from './slider-button.vue'
import tmSheet from '../tm-sheet/tm-sheet.vue'
import tmText from '../tm-text/tm-text.vue'
import TmIcon from '../tm-icon/tm-icon.vue'
import sliderLabel from './slider-label.vue'
import sliderMask from './slider-mask.vue'
// #ifdef APP-PLUS-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
const btn0 = ref<InstanceType<typeof sliderButton> | null>(null)
const btn1 = ref<InstanceType<typeof sliderButton> | null>(null)

const proxy = getCurrentInstance()?.proxy ?? null
const emits = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
  //是否跟随全局主题的变换而变换
  followTheme: {
    type: [Boolean, String],
    default: true,
  },
  //竖向时这个是高度。
  width: {
    type: Number,
    default: 500,
  },
  // 条的高度
  //竖向时，这个是宽度。
  height: {
    type: Number,
    default: 8,
  },
  //按钮的大小。
  buttonSize: {
    type: Number,
    default: 52,
  },
  /**
   * 方向
   * horizontal:水平,
   * vertical:竖向。
   */
  direction: {
    type: String,
    default: 'horizontal',
  },
  color: {
    type: String,
    default: 'primary',
  },
  bgColor: {
    type: String,
    default: 'grey-3',
  },
  max: {
    type: Number,
    default: 100,
  },
  min: {
    type: Number,
    default: 0,
  },
  //默认的值一定要和v-model相同的类型，
  defaultValue: {
    type: [Array, Number] as PropType<number[] | number>,
    default: 0,
  },

  //双向绑定数据可以使用v-model,记住格式一定要和defaultValue相同。
  modelValue: {
    type: [Array, Number],
    default: 0,
  },
  //格式为自己的标签显示 ，是按钮上方的显示 标签。
  formart: {
    type: Function,
    default: () => {
      return (val: number | string) => {
        return val
      }
    },
  },
  //只有这个为true,下面的step才会显示标签。
  showLabel: {
    type: Boolean,
    default: false,
  },
  //需要显示的步骤标签。
  step: {
    type: Number,
    default: 5,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
})

interface btnMovetype {
  x: number
  y: number
}
const width = computed(() => uni.upx2px(props.width))
const isDarg = ref(false)
const _disabled = computed(() => props.disabled)
const btnPos: Ref<Array<btnMovetype>> = ref([
  {
    x: 0,
    y: 0,
  },
  {
    x: 0,
    y: 0,
  },
])
let _x = 0
let _y = 0
let buttonStaticsLeft = 0
let buttonStaticsMaxLeft = width.value
const isNvue = ref(false)
const nvueDetailPos = ref({ left: 0, bottom: 0 })
// #ifdef APP-NVUE
isNvue.value = true
// #endif

const _sliderBarCssWidth = computed(() => {
  if (props.direction == 'horizontal') return props.width + props.buttonSize
  return props.buttonSize
})
const _sliderBarCssHeight = computed(() => {
  if (props.direction == 'horizontal') return props.buttonSize
  return props.width + props.buttonSize
})

//当前操作的是哪个按钮。
const BtnIndex = ref(0)
//显示上访值。
const showDetail = ref(false)
const _valueMax = computed(() => {
  return props.max - props.min
})
const _barWidth = computed(() => {
  return Math.abs(btnPos.value[0].x - btnPos.value[1].x)
})
const _barLet = computed(() => {
  return Math.min(Math.abs(btnPos.value[0].x), Math.abs(btnPos.value[1].x))
})

const _value = ref(0)
//是双滑块，还是单滑块。
const isDablue = ref(Array.isArray(props.defaultValue) ? true : false)

zhuanghuaValue()
watchEffect(() => {
  let val = Math.ceil(
    (Math.abs(btnPos.value[BtnIndex.value].x) / uni.upx2px(props.width)) * _valueMax.value + props.min
  )
  if (typeof props.formart === 'function') {
    let p = props.formart(val)
    if (typeof p === 'function') {
      p = p(val)
    }
    val = p
  }
  _value.value = val
})

const _blackValue = getValue()
watch(
  () => props.modelValue,
  () => {
    zhuanghuaValue(props.modelValue)
  }
)
/**转换值 */
function zhuanghuaValue(defaultValue: number | number[] = props.defaultValue) {
  let maxWidth = uni.upx2px(props.width)
  if (!Array.isArray(defaultValue)) {
    let vsp = Number(defaultValue)
    vsp = Math.min(props.max, Math.max(props.min, vsp))
    btnPos.value[0].x = mapToPercentage(vsp) * maxWidth
  } else {
    let vsp_0 = Number(defaultValue[0])
    vsp_0 = Math.max(props.min, Math.min(props.max, vsp_0))
    btnPos.value[0].x = mapToPercentage(vsp_0) * maxWidth
    let vsp_1 = Number(defaultValue[1])
    vsp_1 = Math.min(Math.max(vsp_1, props.min), props.max)
    btnPos.value[1].x = mapToPercentage(vsp_1) * maxWidth
  }
}

function mapToPercentage(value: number) {
  var minValue = props.min
  var maxValue = props.max
  var percentage = (value - minValue) / (maxValue - minValue)
  return percentage
}

function getValue() {
  if (!isDablue.value) {
    return Math.ceil((Math.abs(btnPos.value[0].x) / uni.upx2px(props.width)) * _valueMax.value + props.min)
  } else {
    return [
      Math.ceil((Math.abs(btnPos.value[0].x) / uni.upx2px(props.width)) * _valueMax.value + props.min),
      Math.ceil((Math.abs(btnPos.value[1].x) / uni.upx2px(props.width)) * _valueMax.value + props.min),
    ]
  }
}

function butnMoveStart(e: btnMovetype, index: number) {
  if (props.disabled) return
  isDarg.value = true

  if (props.direction == 'horizontal') {
    _x = e.x - btnPos.value[index].x
  } else {
    _x = e.y - btnPos.value[index].x
  }

  BtnIndex.value = index
}
function butnMove(e: btnMovetype, index: number) {
  if (props.disabled) return
  if (!isDarg.value) return
  let left = e.x - _x
  if (props.direction != 'horizontal') {
    left = e.y - _x
  }
  if (left < buttonStaticsLeft) {
    left = buttonStaticsLeft
  } else if (left > buttonStaticsMaxLeft) {
    left = buttonStaticsMaxLeft
  }

  btnPos.value[index].x = left
  showDetail.value = true
  getDomRectBound()
}
function butnMoveEnd(e: btnMovetype, index: number) {
  if (props.disabled) return
  isDarg.value = false
  showDetail.value = false
  emits('update:modelValue', getValue())
  emits('change', getValue())
  pushFormItem()
}

function getDomRectBound() {
  // #ifdef APP-NVUE
  dom.getComponentRect(proxy?.$refs['btn' + BtnIndex.value], function (res) {
    if (res?.size) {
      // { ...res.size }
      // console.log(res.size)
      const { left, top } = res.size
      nvueDetailPos.value = {
        left: left - (uni.upx2px(100) - uni.upx2px(props.buttonSize)) / 2,
        bottom: top - 45,
      }
    }
  })
  // #endif
}

/** -----------form专有------------ */
//父级方法。
const rulesObj = inject(
  'tmFormItemRules',
  computed<Array<rulesItem>>(() => {
    return [
      {
        message: '请选择',
        required: false,
        validator: false,
      },
    ]
  })
)
//父级方法。
let parentFormItem: any = proxy?.$parent
while (parentFormItem) {
  if (parentFormItem?.tmFormComnameFormItem == 'tmFormComnameFormItem' || !parentFormItem) {
    break
  } else {
    parentFormItem = parentFormItem?.$parent ?? undefined
  }
}
const validate = (rules: Array<rulesItem>) => {
  rules = rules.map((el) => {
    if (typeof el.validator === 'function' && el.required === true) {
      return el
    } else if (typeof el.validator === 'boolean' && el.required === true) {
      return {
        ...el,
        validator: (val: string | number | Array<string | number>) => {
          if (Array.isArray(val)) {
            return val.reduce((a, b) => Number(a) + Number(b)) == 0
          } else {
            return Number(val) == 0
          }
        },
      }
    } else {
      return {
        ...el,
        validator: (val: string | number) => {
          return true
        },
      }
    }
  })
  let rules_filter: Array<rulesItem> = rules.filter((el) => {
    return typeof el.validator === 'function' && el.required === true
  })
  let _valueSlider = getValue()
  let rules_fun: Array<Promise<rulesItem>> = rules_filter.map((el) => {
    return new Promise(async (res, rej) => {
      if (typeof el.validator === 'function') {
        let vr = await el.validator(_valueSlider)
        if (vr) {
          res({
            message: String(el.message),
            validator: true,
          })
        } else {
          rej({
            message: el.message,
            validator: false,
          })
        }
      } else {
        res({
          message: el.message,
          validator: true,
        })
      }
    })
  })
  return Promise.all(rules_fun)
}

async function pushFormItem(isCheckVail = true) {
  if (parentFormItem) {
    if (isCheckVail) {
      let _valueSlider = getValue()
      validate(toRaw(rulesObj.value))
        .then((ev) => {
          parentFormItem.pushCom({
            value: _valueSlider,
            isRequiredError: false, //true,错误，false正常 检验状态
            componentsName: 'tm-slider', //表单组件类型。
            message: ev.length == 0 ? '' : ev[0].message, //检验信息提示语。
          })
        })
        .catch((er) => {
          parentFormItem.pushCom({
            value: _valueSlider,
            isRequiredError: true, //true,错误，false正常 检验状态
            componentsName: 'tm-slider', //表单组件类型。
            message: er.message, //检验信息提示语。
          })
        })
    }
  }
}
pushFormItem()

const tmFormFun = inject(
  'tmFormFun',
  computed(() => '')
)
watch(tmFormFun, () => {
  if (tmFormFun.value == 'reset') {
    emits('update:modelValue', _blackValue)
    pushFormItem(false)
  }
})

/** -----------end------------ */
</script>
<style scoped></style>
